﻿using Nop.Core;
using Nop.Core.Domain.Messages;
using Nop.Core.Domain.Payments;
using Nop.Plugin.Payments.PayU_ZA.Models;
using Nop.Services.Configuration;
using Nop.Services.CustomExtentions;
using Nop.Services.Logging;
using Nop.Services.Messages;
using Nop.Services.Orders;
using Nop.Services.Payments;
using Nop.Services.Stores;
using Nop.Web.Framework.Controllers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.Mvc;
using System.Xml.Linq;
using System.Xml.XPath;

namespace Nop.Plugin.Payments.PayU_ZA.Controllers {
    public class PaymentPayU_ZAController : BaseNopPaymentController {
        private readonly IWorkContext _workContext;
        private readonly IStoreService _storeService;
        private readonly ISettingService _settingService;
        private readonly IPaymentService _paymentService;
        private readonly IOrderService _orderService;
        private readonly IOrderProcessingService _orderProcessingService;
        private readonly ILogger _logger;
        private readonly IWebHelper _webHelper;
        private readonly IQueuedEmailService _queuedEmailService;
        private readonly PaymentSettings _paymentSettings;
        private readonly INotifyAdmin _notifyAdmin;

        public PaymentPayU_ZAController(IWorkContext workContext,
            IStoreService storeService,
            ISettingService settingService,
            IPaymentService paymentService,
            IOrderService orderService,
            IOrderProcessingService orderProcessingService,
            ILogger logger,
            IWebHelper webHelper,
            IQueuedEmailService queuedEmailService,
            IEmailAccountService emailAccService,
            PaymentSettings paymentSettings,
            INotifyAdmin notifyAdmin) {
            this._workContext = workContext;
            this._storeService = storeService;
            this._settingService = settingService;
            this._paymentService = paymentService;
            this._orderService = orderService;
            this._orderProcessingService = orderProcessingService;
            this._logger = logger;
            this._webHelper = webHelper;
            this._paymentSettings = paymentSettings;
            this._queuedEmailService = queuedEmailService;
            this._notifyAdmin = notifyAdmin;
        }

        [AdminAuthorize]
        [ChildActionOnly]
        public ActionResult Configure() {
            //load settings for a chosen store scope
            var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext);
            var PayU_ZAPaymentSettings = _settingService.LoadSetting<PayU_ZAPaymentSettings>(storeScope);

            var model = new ConfigurationModel();
            model.UseSandbox = PayU_ZAPaymentSettings.UseSandbox;
            model.Safekey = PayU_ZAPaymentSettings.SafeKey;
            model.IpnUrl = PayU_ZAPaymentSettings.IpnUrl;
            model.Password = PayU_ZAPaymentSettings.Password;
            model.UserName = PayU_ZAPaymentSettings.UserName;
            model.PaymentMethods = PayU_ZAPaymentSettings.PaymentMethods;

            model.ActiveStoreScopeConfiguration = storeScope;
            if (storeScope > 0) {
                model.UseSandbox_OverrideForStore = _settingService.SettingExists(PayU_ZAPaymentSettings, x => x.UseSandbox, storeScope);
                model.Safekey_OverrideForStore = _settingService.SettingExists(PayU_ZAPaymentSettings, x => x.SafeKey, storeScope);
                model.IpnUrl_OverrideForStore = _settingService.SettingExists(PayU_ZAPaymentSettings, x => x.IpnUrl, storeScope);
                model.UserName_OverrideForStore = _settingService.SettingExists(PayU_ZAPaymentSettings, x => x.UserName, storeScope);
                model.Password_OverrideForStore = _settingService.SettingExists(PayU_ZAPaymentSettings, x => x.Password, storeScope);
                model.PaymentMethods_OverrideForStore = _settingService.SettingExists(PayU_ZAPaymentSettings, x => x.PaymentMethods, storeScope);

            }

            return View("Nop.Plugin.Payments.PayU_ZA.Views.PaymentPayU_ZA.Configure", model);
        }


        [HttpPost]
        [AdminAuthorize]
        [ChildActionOnly]
        public ActionResult Configure(ConfigurationModel model) {
            if (!ModelState.IsValid)
                return Configure();

            //load settings for a chosen store scope
            var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext);
            var PayU_ZAPaymentSettings = _settingService.LoadSetting<PayU_ZAPaymentSettings>(storeScope);

            //save settings
            PayU_ZAPaymentSettings.UseSandbox = model.UseSandbox;
            PayU_ZAPaymentSettings.SafeKey = model.Safekey;
            PayU_ZAPaymentSettings.IpnUrl = model.IpnUrl;
            PayU_ZAPaymentSettings.UserName = model.UserName;
            PayU_ZAPaymentSettings.Password = model.Password;
            PayU_ZAPaymentSettings.PaymentMethods = model.PaymentMethods;

            /* We do not clear cache after each setting update.
             * This behaviour can increase performance because cached settings will not be cleared 
             * and loaded from database after each update */
            if (model.UseSandbox_OverrideForStore || storeScope == 0)
                _settingService.SaveSetting(PayU_ZAPaymentSettings, x => x.UseSandbox, storeScope, false);
            else if (storeScope > 0)
                _settingService.DeleteSetting(PayU_ZAPaymentSettings, x => x.UseSandbox, storeScope);

            if (model.IpnUrl_OverrideForStore || storeScope == 0)
                _settingService.SaveSetting(PayU_ZAPaymentSettings, x => x.IpnUrl, storeScope, false);
            else if (storeScope > 0)
                _settingService.DeleteSetting(PayU_ZAPaymentSettings, x => x.IpnUrl, storeScope);

            if (model.Safekey_OverrideForStore || storeScope == 0)
                _settingService.SaveSetting(PayU_ZAPaymentSettings, x => x.SafeKey, storeScope, false);
            else if (storeScope > 0)
                _settingService.DeleteSetting(PayU_ZAPaymentSettings, x => x.SafeKey, storeScope);

            if (model.UserName_OverrideForStore || storeScope == 0)
                _settingService.SaveSetting(PayU_ZAPaymentSettings, x => x.UserName, storeScope, false);
            else if (storeScope > 0)
                _settingService.SaveSetting(PayU_ZAPaymentSettings, x => x.UserName, storeScope);

            if (model.Password_OverrideForStore || storeScope == 0)
                _settingService.SaveSetting(PayU_ZAPaymentSettings, x => x.Password, storeScope, false);
            else if (storeScope > 0)
                _settingService.SaveSetting(PayU_ZAPaymentSettings, x => x.Password, storeScope);

            if (model.PaymentMethods_OverrideForStore || storeScope == 0)
                _settingService.SaveSetting(PayU_ZAPaymentSettings, x => x.PaymentMethods, storeScope, false);
            else if (storeScope > 0)
                _settingService.SaveSetting(PayU_ZAPaymentSettings, x => x.PaymentMethods, storeScope);

            //now clear settings cache
            _settingService.ClearCache();

            return Configure();
        }


        [NonAction]
        public override IList<string> ValidatePaymentForm(FormCollection form) {
            var warnings = new List<string>();
            return warnings;
        }

        [NonAction]
        public override ProcessPaymentRequest GetPaymentInfo(FormCollection form) {
            var paymentInfo = new ProcessPaymentRequest();
            return paymentInfo;
        }

        [ChildActionOnly]
        public ActionResult PaymentInfo() {
            return View("Nop.Plugin.Payments.PayU_ZA.Views.PaymentPayU_ZA.PaymentInfo");
        }

        public ActionResult CancelOrder(FormCollection form) {
            return RedirectToAction("Index", "Home", new { area = "" });
        }

        [ValidateInput(false)]
        [AllowAnonymous]
        public ActionResult IPNHandler() {
            try {
                byte[] param = Request.BinaryRead(Request.ContentLength);
                string strRequest = Encoding.ASCII.GetString(param);
                _logger.InsertLog(Core.Domain.Logging.LogLevel.Information, "PAYU_IPN", strRequest);
                XDocument Response = new XDocument(strRequest);
                //get PayU ref
                XElement PayUReference = Response.XPathSelectElement("PaymentNotification/PayUReference");
                if (PayUReference != null) {
                    var PaymentAmountVal = Response.XPathSelectElement("PaymentNotification/Basket/AmountInCents");
                    decimal PaymentAmount = PaymentAmountVal.Value == null ? decimal.Zero : decimal.Parse(PaymentAmountVal.Value) / 100;
                    var CurrencyCodeVal = Response.XPathSelectElement("PaymentNotification/Basket/CurrencyCode");
                    string CurrencyCode = string.IsNullOrWhiteSpace(CurrencyCodeVal.Value) ? "ZAR" : CurrencyCodeVal.Value.ToString();
                    var ResultCodeVal = Response.XPathSelectElement("PaymentNotification/ResultCode");
                    int ResultCode = string.IsNullOrWhiteSpace(ResultCodeVal.Value) ? 0 : int.Parse(ResultCodeVal.Value.ToString());
                    var TransactionTypeVal = Response.XPathSelectElement("PaymentNotification/TransactionType");
                    string TransactionType = string.IsNullOrWhiteSpace(TransactionTypeVal.Value) ? "" : TransactionTypeVal.Value.ToString().ToUpper().Trim();
                    var UniqueRefVal = Response.XPathSelectElement("PaymentNotification/IpnExtraInfo/ResponseHash");
                    string UniqueRef = UniqueRefVal.Value;

                    //Check for Successful payment based on calling back to PayU. This makes fake posts impossible. 
                    var processor = _paymentService.LoadPaymentMethodBySystemName("Payments.PayU_ZA") as PayU_ZAPaymentProcessor;
                    if (processor == null ||
                        !processor.IsPaymentMethodActive(_paymentSettings) || !processor.PluginDescriptor.Installed)
                        throw new NopException("PayU_ZA module cannot be loaded");
                    var CheckResult = processor.ConfirmPayment(PayUReference.Value);
                    if (CheckResult.Value != decimal.Zero) {
                        _orderService.ProcessAPayment(CheckResult.Value, CheckResult.Message, CheckResult.UniqueRef, null, CheckResult.Transcript, CheckResult.IsARefund);
                    }
                }

            }
            catch (Exception ex) {
                _logger.InsertLog(Core.Domain.Logging.LogLevel.Error, "PAYU_IPN_PROCESSING_ERROR", ex.Message + ex.InnerException ?? "");
            }
            return Content("");
        }

        [ValidateInput(false)]
        public ActionResult PDTHandler() {
            var orderID = int.Parse(Request.QueryString["Order"]);
            var PayUReference = Request.QueryString["PayUReference"];
            var order = _orderService.GetOrderById(orderID);
            //check status of order by callback
            var lastPaymentRef = order.Payments.OrderByDescending(f => f.EventDate).LastOrDefault();
            if (lastPaymentRef != null && lastPaymentRef.Reference != null) {
                var processor = _paymentService.LoadPaymentMethodBySystemName("Payments.PayU_ZA") as PayU_ZAPaymentProcessor;
                if (processor == null ||
                    !processor.IsPaymentMethodActive(_paymentSettings) || !processor.PluginDescriptor.Installed)
                    throw new NopException("PayU_ZA module cannot be loaded");
                var CheckResult = processor.ConfirmPayment(lastPaymentRef.Reference);
                if (CheckResult.Value != decimal.Zero) {
                    _orderService.ProcessAPayment(CheckResult.Value, CheckResult.Message, CheckResult.UniqueRef, order, CheckResult.Transcript, CheckResult.IsARefund);
                }
            }

            if (order != null && _orderProcessingService.CanMarkOrderAsPaid(order)) {
                _orderService.UpdateOrder(order);
                _orderProcessingService.MarkOrderAsPaid(order);

                return RedirectToRoute("CheckoutCompleted", new { orderId = order.Id });
            }
            else
                return RedirectToAction("Index", "Home", new { area = "" });
        }
    }
}
